/*
 * 1. Measure the cost of system call
 * about 0.6 clock tick (us)
 * 2. Measure the context switch
 * about 25 clock tick (us)
 * but when affinity is set to same CPU
 * it is reduced to 7/6/10 (us)
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <inttypes.h>
#include <time.h>
#define _GNU_SOURCE
#define __USE_GNU
#include <sched.h>

static const char* dir_path = "dummy";
#define COUNTER 1000

/*
Abstraction
*/

typedef uint64_t (*get_time_func)(void);
typedef void (*print_time_func)(uint64_t);

struct time_counter
{
  /* private */
  get_time_func __get_time;
  print_time_func __print_time;
  uint64_t __time;
};

static void
time_counter_start(struct time_counter* counter)
{
  counter->__time = counter->__get_time();
}

static void
time_counter_stop(struct time_counter* counter)
{
  uint64_t time = counter->__get_time();
  counter->__time = time - counter->__time;
}

static void
time_counter_print(const struct time_counter* counter)
{
  counter->__print_time(counter->__time);
}

/*
 * gettimeofday implementation
 */
static uint64_t
gettimeofday_get_time(void)
{
  struct timeval tv;
  if (gettimeofday(&tv, NULL) == -1)
  {
    exit(EXIT_FAILURE);
  }

  /* ignore seconds */
  return tv.tv_usec;
}

static void
gettimeofday_print_time(uint64_t time)
{
  printf("gettimeofday: %" PRIu64  " us\n", time);
}

/*
 * read time stamp counter implementation
 */
static uint64_t
rdtsc_get_time(void)
{
  uint32_t hi, lo;
  asm("rdtsc"
      : "=a" (lo), "=d" (hi));

  return (uint64_t)hi << 32 | lo;
}

static void
tick_print_time(uint64_t time)
{
  int usec_res = CLOCKS_PER_SEC / 1000000;
  printf("ticks: %" PRIu64  " ticks | resolution: %d clock tick per us\n",
         time,
         usec_res);
}

/*
 * clock glibc implementation
 */
static uint64_t
clock_get_time(void)
{
  return clock();
}

/* system call function */
volatile static void
call_system_call(size_t syscall_num)
{
  int fd;
  if ((fd = open(dir_path, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)) == -1)
    {
      perror("cannot open file");
      exit(EXIT_FAILURE);
    }

  size_t counter = syscall_num;
  char *buff;
  while (counter--)
    {
      read(fd, buff, 0);
    }

  close(fd);
}

/* factory method */
enum measurement_method
  {
   gettimeofday_method,
   rdtsc_method,
   clock_method,
  };

static struct time_counter
obtain_counter(enum measurement_method method)
{
  struct time_counter counter;
  switch(method)
    {
    case gettimeofday_method:
      counter.__get_time = gettimeofday_get_time;
      counter.__print_time = gettimeofday_print_time;
      break;
    case rdtsc_method:
      counter.__get_time = rdtsc_get_time;
      counter.__print_time = tick_print_time;
      break;
    case clock_method:
      counter.__get_time = clock_get_time;
      counter.__print_time = tick_print_time;
      break;
    default:
      fprintf(stderr, "measurement method not supported!\n");
      exit(EXIT_FAILURE);
    }
  counter.__time = 0;

  return counter;
}

static void
print_cpu_affinity(void)
{
  printf("CPU: %d\n", sched_getcpu());
}

static void
place_on_single_cpu(int cpu, pid_t pid)
{
  cpu_set_t cpuset;
  CPU_ZERO(&cpuset);
  CPU_SET(cpu, &cpuset);
  sched_setaffinity(pid, CPU_SETSIZE, &cpuset);
}

int
main(void)
{
  place_on_single_cpu(sched_getcpu(), getpid());
  struct time_counter counter = obtain_counter(clock_method);

  /* measure system call */
  time_counter_start(&counter);
  call_system_call(COUNTER);
  time_counter_stop(&counter);
  printf("measure %dx system call\n", COUNTER);
  time_counter_print(&counter);

  puts("");
  /* measure context switch */
  int pipefd[2];
  if (pipe(pipefd) == -1) {
    perror("pipe");
    exit(EXIT_FAILURE);
  }

  int rc;
  const char *dummy_text = "switched context";
  if ((rc = fork()) == 0) {
    print_cpu_affinity();
    close(pipefd[0]);
    write(pipefd[1], dummy_text, strlen(dummy_text));
    exit(EXIT_SUCCESS);
  }
  print_cpu_affinity();
  close(pipefd[1]);

  char buffer[100];
  ssize_t bytes;
  puts("measure context switch");
  time_counter_start(&counter);
  while ((bytes = read(pipefd[0], buffer, 100)) > 0)
    {
      time_counter_stop(&counter);
      buffer[bytes] = '\0';
      printf("msg receivd: %s\n", buffer);
    }
  time_counter_print(&counter);
  wait(NULL);

  return EXIT_SUCCESS;
}
